/*
** Tile Map System by Neil C. Obremski                        [ Magenta's Maze ]
** 2017 Gibdon Moon Productions                   [ http://magsmaze.gibdon.com ]
**
** This is free and unencumbered software released into the public domain.
**
** Anyone is free to copy, modify, publish, use, compile, sell, or
** distribute this software, either in source code form or as a compiled
** binary, for any purpose, commercial or non-commercial, and by any
** means.
**
** In jurisdictions that recognize copyright laws, the author or authors
** of this software dedicate any and all copyright interest in the
** software to the public domain. We make this dedication for the benefit
** of the public at large and to the detriment of our heirs and
** successors. We intend this dedication to be an overt act of
** relinquishment in perpetuity of all present and future rights to this
** software under copyright law.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
** IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
** OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
** ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
** OTHER DEALINGS IN THE SOFTWARE.
**
** Tested w/ DOSBox 0.74, Microsoft C 5.10
 */
#ifndef __MAG_MAP
#define __MAG_MAP

#include <string.h>
#include <stdlib.h>
#include <time.h>

#include "mag-3d.h"

#ifndef MAP_H
#define MAP_H 40
#endif

#ifndef MAP_W
#define MAP_W 64
#endif

#ifndef SIGHT
#define SIGHT 4
#endif

char map[MAP_H][MAP_W];	/* rows and columns ([y][x]) */

struct {
	char x, y;
	int facing;		/* degrees facing (0=up, 90=right, 180=down, 270=left) */
} pos;

/* maximum number of unique tiles / objects */
#ifndef TILE_MAX
#define TILE_MAX 20
#endif

#define TILE_IS_NULL(t) (!(t).bot && !(t).mid && !(t).top)

#define TILE_MAKE_NULL(t) { (t).bot = (t).mid = (t).top = 0; \
	(t).ch[0] = (t).ch[1] = 0; (t).value = 0; }

/* tile objects indexed by values in map[][] ~ first [0] subscript wasted */
struct tile {
	struct mesh *bot;		/* bottom mesh */
	struct mesh *mid;		/* middle mesh */
	struct mesh *top;		/* top mesh */
	char ch[2];				/* characters to print as texture map */
	char letter;			/* tile letter for loading maps */
	char value;				/* magic cube number */
} tiles[TILE_MAX];

char TILE_CH_EMPTY[2] = { '\0', '\0' };

#define gettile(x,y) (&tiles[map[y][x]])

/* create a tile type */
char addtile(struct mesh *bot, struct mesh *mid, struct mesh *top,
	char letter, char ch[2], char value)
{
	char i;
	for (i = 1; i < TILE_MAX; i++) {
		if (TILE_IS_NULL(tiles[i])) {
			tiles[i].bot = bot;
			tiles[i].mid = mid;
			tiles[i].top = top;
			tiles[i].ch[0] = ch[0];
			tiles[i].ch[1] = ch[1];
			tiles[i].letter = letter;
			tiles[i].value = value;
			break;
		}
	}

	return i;
}

/* clear current map */
void clearmap(char walltile)
{
	int i;

	memset(map, 0, sizeof(map));

	if (walltile) {
		for (i = 0; i < MAP_W; i++) {
			map[0][i] = walltile;
			map[MAP_H-1][i] = walltile;
		}

		for (i = 0; i < MAP_H; i++) {
			map[i][0] = walltile;
			map[i][MAP_W-1] = walltile;
		}
	}

	/* reset player position */
	pos.x = MAP_W >> 1;
	pos.y = MAP_H >> 1;
	pos.facing = 0;
}

void initmap(void)
{
	srand((unsigned int)time(0));
	memset(tiles, 0, sizeof(tiles));
	clearmap(0);
}

char loadmap(char *fn)
{
	char x, y, i, textbuf[80];

	FILE *fp = fopen(fn, "r");

	if (!fp) {
		return FALSE;
	}

	clearmap(0);

	memset(textbuf, ' ', sizeof(textbuf));
	textbuf[sizeof(textbuf)-1] = '\0';
	y = 0;
	while (fgets(textbuf, sizeof(textbuf), fp)) {

		if ('=' == textbuf[0]) { /* =[letter]=[class],[texture] */
			char letter = textbuf[1];
			char tileclass = textbuf[3];
			char *texch = textbuf+5;

			for (i = 0; i < TILE_MAX; i++) {
				if (tiles[i].letter == tileclass) {
					addtile(tiles[i].bot, tiles[i].mid, tiles[i].top,
						letter, texch, 1);
					break;
				}
			}

		} else {
			for (x = 0; y < MAP_H && x < MAP_W; x++) {
				switch (textbuf[x]) {
					case ' ':
					case 0:
						break;

					case '^': /* player facing up */
						pos.facing = 0;
						pos.x = x;
						pos.y = y;
						break;

					case 'v': /* player facing down */
						pos.facing = 180;
						pos.x = x;
						pos.y = y;
						break;

					case '<': /* player facing left */
						pos.facing = 270;
						pos.x = x;
						pos.y = y;
						break;

					case '>': /* player facing right */
						pos.facing = 90;
						pos.x = x;
						pos.y = y;
						break;

					default:
						for (i = 0; i < TILE_MAX; i++) {
							if (tiles[i].letter == textbuf[x]) {
								map[y][x] = i;
								break;
							}
						}
				} /* switch */

			} /* foreach x */

			y++;
		}

		memset(textbuf, ' ', sizeof(textbuf));
		textbuf[sizeof(textbuf)-1] = '\0';

	} /* foreach y */

	return TRUE;
}

/* add tile meshes into rendering pipeline and returns its ID */
char tilepipe(struct tile *pt, int x, int y, int z)
{
	struct mesh *mo = 0;

	char id = rnext;
	int viewrot = -pos.facing;
	if (viewrot) {
		viewrot += 360;
	}

	if (pt->bot) {
		ASSERT_MODEL(pt->bot);
		mo = addmesh(pt->bot, id);
		ASSERT_MESH(mo);
		addpipe(mo);
		texprint(mo, pt->ch);
		rotatey(mo, viewrot);
		translate(mo, x, y, z);
	}

	if (pt->mid) {
		ASSERT_MODEL(pt->mid);
		mo = addmesh(pt->mid, id);
		ASSERT_MESH(mo);
		addpipe(mo);
		texprint(mo, pt->ch);
		rotatey(mo, viewrot);
		translate(mo, x, y, z);
	}

	if (pt->top) {
		ASSERT_MODEL(pt->top);
		mo = addmesh(pt->top, id);
		ASSERT_MESH(mo);
		addpipe(mo);
		texprint(mo, pt->ch);
		rotatey(mo, viewrot);
		translate(mo, x, y, z);
	}

	return id;
}

/* move in current direction and render newly visible tiles there */
char move(char preview)
{
	int upx = ROUND_FX(SIN_FX[pos.facing]);
	int upy = -ROUND_FX(COS_FX[pos.facing]);
	int newx = pos.x + upx;
	int newy = pos.y + upy;
	int x, y, z, z3d, side, leftx, lefty, rightx, righty;

	/* map boundaries */
	if (newx < 0 || newx >= MAP_W || newy < 0 || newy >= MAP_H) {
		return FALSE;
	}

	/* map obstacle */
	if (map[newy][newx]) {
		struct tile *pt = &tiles[map[newy][newx]];
		if (!pt->value) {
			return FALSE;
		}
	}

	/* pre-view the new map tiles */
	if (preview) {
		leftx = ROUND_FX(SIN_FX[pos.facing-90 < 0 ? 270 : pos.facing-90]);
		lefty = -ROUND_FX(COS_FX[pos.facing-90 < 0 ? 270 : pos.facing-90]);

		rightx = ROUND_FX(SIN_FX[pos.facing+90 > 270 ? 0 : pos.facing+90]);
		righty = -ROUND_FX(COS_FX[pos.facing+90 > 270 ? 0 : pos.facing+90]);

		z = SIGHT;
		z3d = (1+z) * 100;

		/* start with middle */
		x = pos.x + (upx * z);
		y = pos.y + (upy * z);
		if (x >= 0 && x < MAP_W && y >= 0 && y < MAP_H && map[y][x]) {
			tilepipe(&tiles[map[y][x]], 0, 0, z3d);
		}

		/* loop over left and right sides */
		for (side = 0; side <= z; side++) {

			/* left side */
			x = pos.x + (upx * z) + (leftx * (1+side));
			y = pos.y + (upy * z) + (lefty * (1+side));
			if (x >= 0 && x < MAP_W && y >= 0 && y < MAP_H && map[y][x]) {
				tilepipe(&tiles[map[y][x]], (1+side)*-100, 0, z3d);
			}

			/* right side */
			x = pos.x + (upx * z) + (rightx * (1+side));
			y = pos.y + (upy * z) + (righty * (1+side));
			if (x >= 0 && x < MAP_W && y >= 0 && y < MAP_H && map[y][x]) {
				tilepipe(&tiles[map[y][x]], (1+side)*100, 0, z3d);
			}
		}
	}

	pos.x = (char)newx;
	pos.y = (char)newy;

	return TRUE;
}

/* change facing -90 degrees and render newly visible tiles there */
char turnleft(char preview)
{
	int x, y, z, x3d, side, upx, upy, leftx, lefty, rightx, righty, newfacing;

	rightx = ROUND_FX(SIN_FX[pos.facing]);
	righty = -ROUND_FX(COS_FX[pos.facing]);

	/* rotate left 90 degrees */
	newfacing = pos.facing - 90;
	if (newfacing < 0) {
		newfacing = 270;
	}

	if (preview) {
		upx = ROUND_FX(SIN_FX[newfacing]);
		upy = -ROUND_FX(COS_FX[newfacing]);

		leftx = ROUND_FX(SIN_FX[newfacing-90 < 0 ? 270 : newfacing-90]);
		lefty = -ROUND_FX(COS_FX[newfacing-90 < 0 ? 270 : newfacing-90]);

		for (z = 0; z < SIGHT; z++) {

			x = pos.x + (upx * z);
			y = pos.y + (upy * z);
			x3d = z * -100;

			/* start with middle */
			if (z > 1 && x >= 0 && x < MAP_W && y >= 0 && y < MAP_H && map[y][x]) {
				tilepipe(&tiles[map[y][x]], x3d, 0, 100);
			}

			/* loop over left and right sides */
			for (side = 0; side <= z; side++) {

				/* left side */
				x = pos.x + (upx * z) + (leftx * (1+side));
				y = pos.y + (upy * z) + (lefty * (1+side));
				if (x >= 0 && x < MAP_W && y >= 0 && y < MAP_H && map[y][x]) {
					tilepipe(&tiles[map[y][x]], x3d, 0, (side)*-100);
				}

				/* right side */
				if (z > 1) {
					x = pos.x + (upx * z) + (rightx * (1+side));
					y = pos.y + (upy * z) + (righty * (1+side));

					if (x >= 0 && x < MAP_W && y >= 0 && y < MAP_H && map[y][x]) {
						tilepipe(&tiles[map[y][x]], x3d, 0, (2+side)*100);
					}
				}
			}
		}
	}

	pos.facing = newfacing;

	return TRUE;
}

/* change facing +90 degrees and render newly visible tiles there */
char turnright(char preview)
{
	int x, y, z, x3d, side, upx, upy, leftx, lefty, rightx, righty, newfacing;

	leftx = ROUND_FX(SIN_FX[pos.facing]);
	lefty = -ROUND_FX(COS_FX[pos.facing]);

	newfacing = pos.facing + 90;
	if (newfacing > 270) {
		newfacing = 0;
	}

	if (preview) {
		upx = ROUND_FX(SIN_FX[newfacing]);
		upy = -ROUND_FX(COS_FX[newfacing]);

		rightx = ROUND_FX(SIN_FX[newfacing+90 > 270 ? 0 : newfacing+90]);
		righty = -ROUND_FX(COS_FX[newfacing+90 > 270 ? 0 : newfacing+90]);

		for (z = 0; z < SIGHT; z++) {

			x = pos.x + (upx * z);
			y = pos.y + (upy * z);
			x3d = z * 100;

			/* start with middle */
			if (z > 1 && x >= 0 && x < MAP_W && y >= 0 && y < MAP_H && map[y][x]) {
				tilepipe(&tiles[map[y][x]], x3d, 0, 100);
			}

			/* loop over left and right sides */
			for (side = 0; side <= z; side++) {

				/* left side */
				if (z > 1) {
					x = pos.x + (upx * z) + (leftx * (1+side));
					y = pos.y + (upy * z) + (lefty * (1+side));
					if (x >= 0 && x < MAP_W && y >= 0 && y < MAP_H && map[y][x]) {
						tilepipe(&tiles[map[y][x]], x3d, 0, (2+side)*100);
					}
				}

				/* right side */
				x = pos.x + (upx * z) + (rightx * (1+side));
				y = pos.y + (upy * z) + (righty * (1+side));

				if (x >= 0 && x < MAP_W && y >= 0 && y < MAP_H && map[y][x]) {
					tilepipe(&tiles[map[y][x]], x3d, 0, (side)*-100);
				}
			}
		}
	}

	pos.facing = newfacing;

	return TRUE;
}

/* renders tiles to 3D from current position */
void viewmap(void)
{
	int x, y, z, z3d, side, upx, upy, leftx, lefty, rightx, righty;

	upx = ROUND_FX(SIN_FX[pos.facing]);
	upy = -ROUND_FX(COS_FX[pos.facing]);

	leftx = ROUND_FX(SIN_FX[pos.facing-90 < 0 ? 270 : pos.facing-90]);
	lefty = -ROUND_FX(COS_FX[pos.facing-90 < 0 ? 270 : pos.facing-90]);

	rightx = ROUND_FX(SIN_FX[pos.facing+90 > 270 ? 0 : pos.facing+90]);
	righty = -ROUND_FX(COS_FX[pos.facing+90 > 270 ? 0 : pos.facing+90]);

	for (z = 0; z < SIGHT; z++) {

		x = pos.x + (upx * z);
		y = pos.y + (upy * z);
		z3d = (1+z) * 100;

		/* start with middle */
		if (x >= 0 && x < MAP_W && y >= 0 && y < MAP_H && map[y][x]) {
			tilepipe(&tiles[map[y][x]], 0, 0, z3d);
		}

		/* loop over left and right sides */
		for (side = 0; side <= z; side++) {

			/* left side */
			x = pos.x + (upx * z) + (leftx * (1+side));
			y = pos.y + (upy * z) + (lefty * (1+side));
			if (x >= 0 && x < MAP_W && y >= 0 && y < MAP_H && map[y][x]) {
				tilepipe(&tiles[map[y][x]], (1+side)*-100, 0, z3d);
			}

			/* right side */
			x = pos.x + (upx * z) + (rightx * (1+side));
			y = pos.y + (upy * z) + (righty * (1+side));

			if (x >= 0 && x < MAP_W && y >= 0 && y < MAP_H && map[y][x]) {
				tilepipe(&tiles[map[y][x]], (1+side)*100, 0, z3d);
			}
		}
	}
}

#endif
